import { loadJsonFile } from 'load-json-file';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import fs from 'fs/promises';
import ts from 'typescript';

const currentPath = path.dirname(fileURLToPath(import.meta.url));
const packageRoot = path.resolve(currentPath, '..', '..');
const pathToThisFile = path.relative(packageRoot, fileURLToPath(import.meta.url));

interface PackageJson {
  exports: Record<
    string,
    {
      import: {
        types: string;
        default: string;
      };
      require: {
        types: string;
        default: string;
      };
    }
  >;
}

const packageJson = await loadJsonFile<PackageJson>(path.resolve(packageRoot, 'package.json'));

async function writeFile(filePath: string, contents: string) {
  const dirname = path.dirname(filePath);
  await fs.mkdir(dirname, { recursive: true });
  return fs.writeFile(
    path.join(packageRoot, filePath),
    `// Auto-generated by ${pathToThisFile}. Do not edit.
// prettier-ignore
/* eslint-disable */

${contents}\n`,
    'utf8'
  );
}

for (const [exportKey, exportValue] of Object.entries(packageJson.exports)) {
  const importObj = exportValue.import;
  const requireObj = exportValue.require;

  const filePath = exportKey === '.' ? 'index' : exportKey.slice('./'.length);

  const getPathToDest = (dest: string) => `./${path.relative(path.dirname(filePath), dest)}`;

  const program = ts.createProgram({
    rootNames: [importObj.types],
    options: {
      module: ts.ModuleKind.ESNext,
    },
  });
  const checker = program.getTypeChecker();
  const sourceFile = program.getSourceFile(importObj.types);
  if (!sourceFile) {
    throw new Error(`Failed to find source file: ${importObj.types}`);
  }

  const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
  if (!moduleSymbol) {
    throw new Error(`Failed to find module symbol for file: ${importObj.types}`);
  }

  const exports = checker.getExportsOfModule(moduleSymbol);

  const namedExports: string[] = [];
  let hasDefaultExport = false;

  for (const exp of exports) {
    if ((exp.escapedName as string) === 'default') {
      hasDefaultExport = true;
    } else {
      namedExports.push(exp.escapedName as string);
    }
  }

  const foundAnyExport = hasDefaultExport || namedExports.length;

  if (!foundAnyExport) {
    throw new Error(`Could not find any exports for file ${importObj.types}`);
  }

  if (hasDefaultExport) {
    throw new Error('create-entry-points.ts does not support default exports yet.');
  }

  const typeFileDestContents = `export { ${namedExports.join(', ')} } from '${getPathToDest(importObj.types)}';
  `;

  await writeFile(`${filePath}.js`, `export * from '${getPathToDest(importObj.default)}'`);
  await writeFile(`${filePath}.d.ts`, typeFileDestContents);
  await writeFile(`${filePath}.cjs`, `module.exports = require('${getPathToDest(requireObj.default)}')`);
}
